package monitor

import (
	"fmt"
	"strings"
	"time"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/binocle"
	"gddgame.cc/galaxy/binocle/plugins/notice"
	"gddgame.cc/galaxy/binocle/plugins/report"
	. "gddgame.cc/galaxy/common/dsl/db"
	. "gddgame.cc/galaxy/common/dsl/web"
	"gddgame.cc/galaxy/server/composer"
	"gddgame.cc/galaxy/server/web"
	"gddgame.cc/galaxy/utils"
	"github.com/robfig/cron"
)

const PluginName = "monitor"

var (
	currentClient *cluster.Client
	monitorCron   *cron.Cron
)

func init() {
	RegisterPlugin(PluginProvider{
		Name:    PluginName,
		Depends: []string{notice.PluginName, report.PluginName},

		OnInit: func(builder composer.Builder, core binocle.CoreClient) error {
			core.OnFinish(func() {
				core.OnObserver(func(observer string) {
					reloadMonitor(builder, core)
				})
				reloadMonitor(builder, core)
			})
			core.MountClient(func(client *cluster.Client) {
				currentClient = client

				client.On("reload/monitor", func(message asteroid.Message) {
					reloadMonitor(builder, core)
				})
			})
			AddTable(&Table{
				Model: ServerMonitorModel,
			})
			return nil
		},
		BindRouter: BindRouter,
	})
}

func BindRouter(builder composer.Builder, core binocle.CoreClient, r *SwaggerRouter) {
	router := r.Router
	router.Put("/server/{server}/reload/monitor", func(context web.Context, next web.Next) error {
		currentClient.All().Send("reload/monitor")
		return next(nil)
	})
}

func reloadMonitor(builder composer.Builder, core binocle.CoreClient) {
	builder.Infof("[ Monitor ] Reload Monitor Start")
	client := core.Client()
	// 加载所有监控
	servers := core.Observers("")
	tmpCron := cron.New()
	for key := range servers {
		if client.CurrentNode() == core.AllocObserver(key) {
			monitors, err := GetMonitorAll(builder, key)
			if err != nil || len(monitors) == 0 {
				builder.Debugf("[ Monitor ] Server %s monitor is empty", key)
				continue
			}
			for _, monitor := range monitors {
				// 计算所有rule最小间隔时间
				min := Minute30Period
				for _, rule := range monitor.Rules {
					if rule.Period < min {
						min = rule.Period
					}
				}
				spec := getSchedule(min)
				builder.Debugf("[ Monitor ] Server %s Add %d cron: %s", key, monitor.Id, spec)
				_ = tmpCron.AddFunc(spec, func() {
					monitorProcess(&monitor, builder, core)
				})
			}
		} else {
			builder.Debugf("[ Monitor ] Server %s alloc %s, local: %s", key, core.AllocObserver(key), client.CurrentNode())
		}
	}
	if monitorCron != nil {
		monitorCron.Stop()
	}
	monitorCron = tmpCron
	monitorCron.Start()
	builder.Infof("[ Monitor ] Reload Monitor End")
}

func monitorProcess(monitor *ServerMonitor, builder composer.Builder, core binocle.CoreClient) {
	var data map[string]interface{}
	var message []string
	now := time.Now()
	if monitor.LastTime != nil {
		if monitor.LastTime.Add(getDuration(monitor.Silent)).After(now) {
			// 在沉默期
			builder.Debugf("[ Monitor ] %d in silent, LastTime: %s, wait %s", monitor.Id, monitor.LastTime, getDuration(monitor.Silent))
			return
		}
	}
	if monitor.IsWhole {
		// rule全部符合才报警
		flag := false
		for _, rule := range monitor.Rules {
			cycle, duration := getCycleAndDuration(&rule)
			r := core.Reports(monitor.Server, []string{rule.Metric}, cycle, duration)
			//fmt.Println(rule, r, cycle, duration)
			// 一次不符合，即可忽略
			if !ruleProcess(&rule, r[0]) {
				flag = true
				break
			}
		}
		if !flag {
			data = map[string]interface{}{
				"server": monitor.Server,
				"name":   monitor.Name,
			}
			goto Silent
		}
	} else {
		nodes := core.NodeList(monitor.Server)
		// rule全部符合才报警
		tmp := []string{}
		for _, node := range nodes {
			flag := false
			for _, rule := range monitor.Rules {
				cycle, duration := getCycleAndDuration(&rule)
				r := core.NodeReports(node, []string{rule.Metric}, cycle, duration)
				// 一次不符合，即可移除该节点
				if !ruleProcess(&rule, r[0]) {
					flag = true
					break
				}
			}
			if !flag {
				tmp = append(tmp, node)
			}
		}
		if len(tmp) > 0 {
			data = map[string]interface{}{
				"server": monitor.Server,
				"name":   monitor.Name,
				"nodes":  strings.Join(tmp, ","),
			}
			goto Silent
		}
	}
	builder.Debugf("[ Monitor ] %d is not match, pass", monitor.Id)
	goto Return
Silent:
	// 统一描述信息
	message = []string{}
	for _, rule := range monitor.Rules {
		message = append(message, fmt.Sprintf("%s(%d) %s %d", rule.Metric, rule.Count, rule.Relation, rule.Value))
	}
	data["message"] = strings.Join(message, ",")
	_ = notice.SendNotice(builder, monitor.Publish, data)
	// 进入沉默时间
	monitor.LastTime = &now
	_ = EditMonitor(builder, monitor.Id, map[string]interface{}{
		"last_time": monitor.LastTime,
	})
Return:
	return
}

func ruleProcess(rule *Rule, result []interface{}) bool {
	// 数据长度不足
	if len(result) < rule.Count*2 {
		return false
	}
	switch rule.Logic {
	case AlwaysLogic:
		for i := 0; i < len(result); i += 2 {
			if !judgeRelation(rule, utils.ToInt32(result[i+1])) {
				return false
			}
		}
		return true
	case OnceLogic:
		for i := 0; i < len(result); i += 2 {
			if judgeRelation(rule, utils.ToInt32(result[i+1])) {
				return true
			}
		}
		return false
	case AvgLogic:
		value := int32(0)
		for i := 0; i < len(result); i += 2 {
			value += utils.ToInt32(result[i+1])
		}
		return judgeRelation(rule, value)
	}
	return false
}

func judgeRelation(rule *Rule, v int32) bool {
	switch rule.Relation {
	case GeqRelation:
		if v >= rule.Value {
			return true
		}
	case LeqRelation:
		if v <= rule.Value {
			return true
		}
	case EqRelation:
		if v == rule.Value {
			return true
		}
	case GqRelation:
		if v > rule.Value {
			return true
		}
	case LqRelation:
		if v < rule.Value {
			return true
		}
	}
	return false
}

func getCycleAndDuration(rule *Rule) (report.MetricCycle, time.Duration) {
	switch rule.Period {
	case Minute1Period:
		return report.Minute1Cycle, time.Minute * time.Duration(rule.Count) * 2
	case Minute5Period:
		return report.Minute5Cycle, time.Minute * time.Duration(rule.Count) * 5 * 2
	case Minute15Period:
		return report.Minute15Cycle, time.Minute * time.Duration(rule.Count) * 15 * 2
	case Minute30Period:
		return report.Minute30Cycle, time.Minute * time.Duration(rule.Count) * 30 * 2
	}
	return 0, 0
}

func getDuration(period SilentPeriod) time.Duration {
	switch period {
	case Minute1Period:
		return time.Minute
	case Minute5Period:
		return time.Minute * 5
	case Minute15Period:
		return time.Minute * 15
	case Minute30Period:
		return time.Minute * 30
	}
	return 0
}
func getSchedule(period RulePeriod) string {
	switch period {
	case Minute1Period:
		return "0 * * * * *"
	case Minute5Period:
		return "0 */5 * * * *"
	case Minute15Period:
		return "0 */15 * * * *"
	case Minute30Period:
		return "0 */30 * * * *"
	}
	return ""
}
